Skip to main content

Displaying, Managing, and Deleting Verifiable Credentials

In this section, we'll walk through how to display and manage verifiable credentials associated with a business contact using the One37ID SDK. We’ll implement a screen where users can view, delete, and initiate new flows for exchanging credentials.

Objective

We will create a screen called BusinessContactCredentialsScreen, which:

  1. Displays the name of a business contact.
  2. Has tabs for viewing Received and Sent Credentials.
  3. Allows the user to delete received credentials.
  4. Includes a "+" button to allow the user to start a new credential flow with the contact.

Step 1: Passing the Contact via Route Parameters

In this example, the contact details are passed as params through React Navigation. This allows you to retrieve the contact information when navigating to the BusinessContactCredentialsScreen.

When you navigate to this screen from elsewhere in the app, you’ll pass the contact object like this:

navigation.navigate("BusinessContactCredentialsScreen", { contact });

The contact information will then be accessible via route.params.

Step 2: Setting Up the BusinessContactCredentialsScreen

This screen will display the credentials and flows related to the contact and allow credential deletion. It also allows initiating new flows.

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, SafeAreaView, TouchableOpacity, FlatList, ActivityIndicator, Alert } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { useRoute, useNavigation } from '@react-navigation/native';
import { Contact, ContactFlow } from '@one37id/mobile-js-sdk';
import { initializeAgent } from '../../one37Agent';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { AddCredentialModal } from '../../components/contact/AddCredentialModal';

const Tab = createMaterialTopTabNavigator();

// Function to handle credential deletion and update state
const deleteCredential = async (credentialId: string, credentials: [], setCredentials: any) => {
const myAgent = await initializeAgent();
if (!myAgent) return;
try {
const result = await myAgent.credentialManager.deleteById([credentialId]);
console.log(`---res`, result);

// Check if the deletion was successful
if (result.isSuccessful) {
// Remove the deleted credential from the state
setCredentials(credentials.filter(credential => credential.id !== credentialId));
}
} catch (error) {
console.error(`Failed to delete credential ${credentialId}. Error: ${error}`);
}
};

// Confirmation alert before deleting
const showDeleteConfirmation = (credentialId: string, credentials: [], setCredentials: any) => {
Alert.alert(
'Delete Credential',
'Are you sure you want to delete this verifiable credential?',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Yes',
onPress: () => deleteCredential(credentialId, credentials, setCredentials),
},
],
{ cancelable: true }
);
};

// Received Credentials Tab
const ReceivedCredentialsTab = ({ credentials, setCredentials }) => (
<View style={styles.tabContent}>
{credentials.length === 0 ? (
<Text style={styles.noCredentialsText}>No received credentials yet.</Text>
) : (
<FlatList
data={credentials}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<View style={styles.credentialCard}>
{/* Delete Icon */}
<TouchableOpacity
style={styles.deleteIconContainer}
onPress={() => showDeleteConfirmation(item.id, credentials, setCredentials)}
>
<Ionicons name="trash" size={24} color="red" />
</TouchableOpacity>

{/* Credential Information */}
<Text style={styles.title}>Verified Email Credential</Text>
<Text style={styles.emailLabel}>
<Text style={styles.label}>Email:</Text> {item.attributes.email}
</Text>
<Text style={styles.infoText}>
<Text style={styles.label}>Category:</Text> {item.attributes._category}
</Text>
<Text style={styles.infoText}>
<Text style={styles.label}>Trust Level:</Text> {item.attributes._trustlevel}
</Text>
<Text style={styles.infoText}>
<Text style={styles.label}>Issuer:</Text> {item.issuerDid}
</Text>
<Text style={styles.infoText}>
<Text style={styles.label}>Issued At:</Text> {new Date(item.issuanceDate).toLocaleDateString()}
</Text>
<Text style={styles.infoText}>
<Text style={styles.label}>Expires At:</Text> {new Date(item.expirationDate).toLocaleDateString()}
</Text>
</View>
)}
/>
)}
</View>
);

// Sent Credentials Tab (Can be customized later)
const SentCredentialsTab = () => (
<View style={styles.tabContent}>
<Text>Sent Credentials</Text>
</View>
);

const BusinessContactCredentialsScreen = () => {
const navigation = useNavigation();
const route = useRoute();
const [contactName, setContactName] = useState<string>('');
const [contactFlows, setContactFlows] = useState<ContactFlow[]>([]);
const [modalVisible, setModalVisible] = useState<boolean>(false);
const [loadingFlows, setLoadingFlows] = useState<boolean>(true);
const contact: Contact = route.params as Contact;
const [credentials, setCredentials] = useState([]);

console.log('---credentials', credentials);

// Set the contact name from the passed route params
useEffect(() => {
setContactName(route.params?.displayName || 'Contact Name');
}, [route.params]);

// Fetch flows and credentials when the contact changes
useEffect(() => {
const fetchFlows = async () => {
setLoadingFlows(true);
const flows = await getFlows(contact.id);
if (flows) {
setContactFlows(flows);
}
setLoadingFlows(false);
};

const fetchCredentials = async () => {
const credentials = await getVerifiableCredentials();
if (credentials) {
setCredentials(credentials);
}
};

fetchFlows();
fetchCredentials();
}, [contact.id]);

// Fetch the flows associated with the contact
const getFlows = async (connectionId: string): Promise<ContactFlow[] | undefined> => {
const myAgent = await initializeAgent();
if (!myAgent) return undefined;
await myAgent.contactManager.fetchBusinessData({ id: connectionId }, [1, 2]);
const result = await myAgent.contactManager.getFlows({ id: connectionId }, [1, 2]);
if (!result.isSuccessful) {
console.error(`[GET BUSINESS FLOWS] Failed to get flows. Error: ${result.error}`);
return undefined;
}
return result.result;
};

// Fetch the verifiable credentials
const getVerifiableCredentials = async () => {
const myAgent = await initializeAgent();
if (!myAgent) return undefined;
const result = await myAgent.credentialManager.getList({ currentPage: 1, rowsPerPage: 1000 , issuerDid: contact.theirDid});
return result.result;
};

// Add a plus icon and back button in the header
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack()}>
<Ionicons name="arrow-back" size={30} color="#fff" />
</TouchableOpacity>
),
headerRight: () => (
<TouchableOpacity onPress={() => {
if (!loadingFlows) {
setModalVisible(true);
}
}}>
<Ionicons name="add-circle-outline" size={30} color="#fff" />
</TouchableOpacity>
),
});
}, [navigation, loadingFlows]);

return (
<SafeAreaView style={styles.container}>
{/* Display the contact name */}
<View style={styles.header}>
<Text style={styles.headerText}>{contactName}</Text>
<TouchableOpacity onPress={() => {
if (!loadingFlows) {
setModalVisible(true);
}
}}>
<Ionicons name="add-circle-outline" size={30} color="#fff" />
</TouchableOpacity>
</View>

{/* Tabs for Received and Sent Credentials */}
<Tab.Navigator
screenOptions={{
tabBarLabelStyle: { fontSize: 14, fontWeight: 'bold' },
tabBarStyle: { backgroundColor: '#fff' },
tabBarIndicatorStyle: { backgroundColor: '#000' },
}}
>
<Tab.Screen name="Received">
{() => <ReceivedCredentialsTab credentials={credentials} setCredentials={setCredentials} />}
</Tab.Screen>
<Tab.Screen name="Sent" component={SentCredentialsTab} />
</Tab.Navigator>

{/* Show a loading indicator when fetching flows */}
{loadingFlows && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color="#6200EE" />
</View>
)}

{/* Modal to add new credentials */}
<AddCredentialModal
modalVisible={modalVisible}
setModalVisible={setModalVisible}
flows={contactFlows}
contactName={contactName}
/>
</SafeAreaView>
);
};

// Styles for the screen
const styles = StyleSheet.create({
container: {
flex

: 1,
backgroundColor: '#F5F5F5',
},
header: {
padding: 20,
backgroundColor: '#6200EE',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
headerText: {
color: '#fff',
fontSize: 20,
fontWeight: 'bold',
},
tabContent: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
credentialItem: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
width: '100%',
},
tabContent: {
flex: 1,
padding: 16,
backgroundColor: '#f5f5f5',
},
noCredentialsText: {
textAlign: 'center',
fontSize: 16,
color: '#999',
marginTop: 20,
},
credentialCard: {
backgroundColor: '#fff',
borderRadius: 8,
padding: 16,
marginVertical: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 5,
position: 'relative',
},
deleteIconContainer: {
position: 'absolute',
top: 10,
right: 10,
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8,
color: '#6200EE',
},
emailLabel: {
fontSize: 16,
marginBottom: 4,
},
label: {
fontWeight: 'bold',
},
infoText: {
fontSize: 14,
marginBottom: 4,
color: '#555',
},
loadingOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.3)',
justifyContent: 'center',
alignItems: 'center',
},
});

export default BusinessContactCredentialsScreen;

Step 3: Explanation of Key Features

  1. Contact Passed via Params:

    • The contact is passed as a route parameter (route.params). This object contains the contact's ID, display name, and other relevant details. It is accessed within the BusinessContactCredentialsScreen.
  2. Fetching and Displaying Credentials:

    • The ReceivedCredentialsTab displays received credentials fetched from the One37ID SDK. The credentials are displayed in a card format, showing attributes such as email, category, and issuer.
  3. Deleting Credentials:

    • When the delete icon is clicked, a confirmation prompt appears. If the user confirms, the credential is deleted via the One37ID SDK, and the credential is removed from the list by updating the state.
  4. Plus Button to Start a Flow:

    • A "+" button is added to the header. When clicked, it opens the AddCredentialModal component, allowing the user to initiate a new credential flow with the contact.
X

Graph View